##
## Makefile for building Erlang applications
##

#
# useful constants and functions
#
EMPTY :=
COMMA :=,
exist = $(wildcard $(1))
escape_quote = $(subst ",\",$(1))
to_upper = \
$(subst \
a,A,$(subst \
b,B,$(subst \
c,C,$(subst \
d,D,$(subst \
e,E,$(subst \
f,F,$(subst \
g,G,$(subst \
h,H,$(subst \
i,I,$(subst \
j,J,$(subst \
k,K,$(subst \
l,L,$(subst \
m,M,$(subst \
n,N,$(subst \
o,O,$(subst \
p,P,$(subst \
q,Q,$(subst \
r,R,$(subst \
s,S,$(subst \
t,T,$(subst \
u,U,$(subst \
v,V,$(subst \
w,W,$(subst \
x,X,$(subst \
y,Y,$(subst \
z,Z,$(1)))))))))))))))))))))))))))

#
# default values
#
DEFAULT_APP_VSN:=0.0.0

#
# directories and files
#
ENV_MK:=../../env.mk
ALT_ENV_MK:=../env.mk
VSN_MK:=../vsn.mk
APP_MK:=../app.mk

SRC_DIR:=.
EBIN_DIR:=../ebin
INCLUDE_DIR:=../include
DOC_DIR:=../doc
OVERVIEW_DIR:=$(SRC_DIR)

DOC_INDEX:=$(DOC_DIR)/index.html
OVERVIEW:=$(OVERVIEW_DIR)/overview.edoc
SPECIFIC_MK:=$(SRC_DIR)/specific.mk

#
# do include
#
-include $(ENV_MK)
-include $(ALT_ENV_MK)
-include $(VSN_MK)
-include $(APP_MK)

#
# confirm APP_NAME and APP_VSN
#
ifndef APP_NAME
 pwd_url = $(subst $(EMPTY) ,%20,$(subst %,%25,$(shell pwd)))

 this_path := $(pwd_url)
 parent_path := $(dir $(this_path))
 parent_path_list := $(subst /, ,$(parent_path))
 app_name := $(lastword $(parent_path_list))
 ifdef app_name
  APP_NAME := $(app_name)
 else
  APP_NAME := $(error Cannot define APP_NAME) # Stop!
 endif # app_name
endif # !APP_NAME

# now, APP_NAME is defined!

APP_NAME_UPPER := $(call to_upper,$(APP_NAME))

ifndef APP_VSN
 ifdef $(APP_NAME_UPPER)_VSN
  APP_VSN:=$($(APP_NAME_UPPER)_VSN)
 else
  APP_VSN:=$(DEFAULT_APP_VSN)
 endif # $(APP_NAME_UPPER)_VSN
endif # !APP_VSN

#
# debug stuffs
#
ifdef debug
 DEBUG:=$(debug)
endif

ifdef DEBUG
 ERLC_DEBUG_FLAG+=-Ddebug -DDEBUG +debug_info
endif

#
# commands and flags
#
ERLC:=erlc
ERLC_INC_FLAG+=-I $(INCLUDE_DIR) -I $(SRC_DIR)
ERLC_FLAGS+= $(ERLC_INC_FLAG) $(ERLC_DEBUG_FLAG) \
 $(ERLC_EXTRA_FLAGS) -o $(EBIN_DIR)

EDOC_OPTS+={def,{version, "$(APP_VSN)"}}, \
 {def,{app_name, "$(APP_NAME)"}}, \
 {dir, "$(DOC_DIR)"}, {overview, "$(OVERVIEW)"}, private

ifdef EDOC_EXTRA_OPTS
 ESCAPED_EDOC_OPTS_LIST := \
  [$(call escape_quote,$(EDOC_OPTS)$(COMMA) $(EDOC_EXTRA_OPTS))]
else
 ESCAPED_EDOC_OPTS_LIST := \
  [$(call escape_quote,$(EDOC_OPTS))]
endif # EDOC_EXTRA_OPTS

#
# sources and targets
#
-include $(SPECIFIC_MK)
ifndef SOURCES
 SOURCES:=$(filter-out $(SRC_DIR)/tmp%,$(wildcard $(SRC_DIR)/*.erl))
 ifdef NOT_SOURCES
  SOURCES:=$(filter-out $(NOT_SOURCES),$(SOURCES))
 endif # NOT_SOURCES
endif # !SOURCES

ifndef INCLUDES
 INCLUDES:= \
  $(filter-out $(INCLUDE_DIR)/tmp%,$(wildcard $(INCLUDE_DIR)/*.hrl))\
  $(filter-out $(SRC_DIR)/tmp%,$(wildcard $(SRC_DIR)/*.hrl))
 ifdef NOT_INCLUDES
  INCLUDES:=$(filter-out $(NOT_INCLUDES),$(INCLUDES))
 endif # NOT_INCLUDES
endif # !INCLUDES

OBJECTS:=$(patsubst $(SRC_DIR)/%.erl, $(EBIN_DIR)/%.beam,$(SOURCES))
# application resource file.
APP_TARGET:=$(EBIN_DIR)/$(APP_NAME).app
# application upgrade file
APPUP_TARGET:=$(EBIN_DIR)/$(APP_NAME).appup

app_src_full:=$(SRC_DIR)/$(APP_NAME).app.src
app_src_simple:=$(SRC_DIR)/app.src
appup_src_full:=$(SRC_DIR)/$(APP_NAME).appup.src
appup_src_simple:=$(SRC_DIR)/appup.src
APP_SRC:=$(if $(call exist,$(app_src_full)),$(app_src_full),$(app_src_simple))
APPUP_SRC:=$(if $(call exist,$(appup_src_full)),$(appup_src_full),$(appup_src_simple))

TEST_MODS:=$(patsubst $(SRC_DIR)/%.erl,%,$(wildcard $(SRC_DIR)/t_*.erl))
all_mods :=$(patsubst $(SRC_DIR)/%.erl,%,$(SOURCES))
mods:=$(filter-out $(TEST_MODS),$(all_mods))
MODS_COMMA_LIST:=$(subst $(EMPTY) ,$(COMMA) ,$(mods))

#
# parser rules
#
YECC_SOURCES:=$(wildcard $(SRC_DIR)/*.yrl)
PARSER_SRC:=$(patsubst $(SRC_DIR)/%.yrl,$(SRC_DIR)/%_parser.erl,$(YECC_SOURCES))
OBJECTS+=$(patsubst $(SRC_DIR)/%.erl, $(EBIN_DIR)/%.beam,$(PARSER_SRC))

#
# check doc/overview.edoc
#
danger := $(if $(call exist,$(DOC_DIR)/overview.edoc),danger,)
ifdef danger
 _exit := $(error Move '$(DOC_DIR)/overview.edoc' to '$(SRC_DIR)/overview.edoc')
endif # danger

#
# main targets
#
.PHONY: obj doc desc all test
obj: $(OBJECTS)

doc: $(DOC_INDEX)

desc: $(APP_TARGET) $(APPUP_TARGET)

all: obj doc desc

test: obj
	@for t in dummy $(TEST_MODS); do \
	  if [ "$$t" != "dummy" ] ; then \
	    echo "Test module: $$t"; \
	    erl -boot start_clean -noshell -pa $(EBIN_DIR) \
	    -s $$t test \
	    -s init stop;\
	  fi \
	done

#
# rules
#
$(OBJECTS) : $(INCLUDES) # each objct depends on include files.

$(OBJECTS) : $(EBIN_DIR)/%.beam : $(SRC_DIR)/%.erl
	$(ERLC) $(ERLC_FLAGS) $<

$(PARSER_SRC) : $(SRC_DIR)/%_parser.erl : $(SRC_DIR)/%.yrl
	erl -boot start_clean -noshell \
	-eval "yecc:yecc(\"$<\", \"$@\")" \
	-s init stop

$(VSN_MK) :
	echo "$(APP_NAME_UPPER)_VSN = $(APP_VSN)" > $@

$(APP_MK) :
	echo "APP_NAME = $(APP_NAME)" > $@
	echo "ERLC_EXTRA_FLAGS=" >> $@
	echo "EDOC_EXTRA_OPTS="  >> $@
	echo "DEBUG="  >> $@

$(OVERVIEW) :
	echo "@title {@app_name}" > $@
	echo "@version {@version}" >> $@

$(APP_SRC) :
	echo "{application, %NAME%," > $@
	echo " [{description, \"\"}," >> $@
	echo "  {vsn, \"%VSN%\"}," >> $@
	echo "  {modules, [%MODS%]}," >> $@
	echo "  {registered,[]}," >> $@
	echo "  {applications, [kernel, stdlib]}]}." >> $@

$(APPUP_SRC) :
	echo "{\"%VSN%\",[],[]}." > $@

$(DOC_INDEX): $(SOURCES) $(OVERVIEW) $(VSN_MK) $(APP_MK)
	erl -boot start_clean -noshell \
	-eval "edoc:application($(APP_NAME), \"$(SRC_DIR)\", \
	  $(ESCAPED_EDOC_OPTS_LIST))" \
	-s init stop

$(APP_TARGET): $(SRC_DIR)/$(APP_SRC) $(VSN_MK) $(APP_MK)
	sed -e 's;%VSN%;$(APP_VSN);' -e 's;%NAME%;$(APP_NAME);' \
	-e 's;%MODS%;$(MODS_COMMA_LIST);' $< > $@

$(APPUP_TARGET): $(APPUP_SRC) $(VSN_MK) $(APP_MK)
	sed -e 's;%VSN%;$(APP_VSN);' -e 's;%NAME%;$(APP_NAME);' $< > $@

#
# other targets
#
.PHONY: clean doc_clean print_vars
clean:
	-rm -f $(OBJECTS) $(APP_TARGET) $(APPUP_TARGET)
	-rm -f $(PARSER_SRC)
	-rm -f $(SRC_DIR)/*.beam $(SRC_DIR)/erl_crash.dump

doc_clean:
	-rm -f $(DOC_DIR)/*

print_vars: # for debugging Makefile
	@echo "ERLANG_HOME=$(ERLANG_HOME)"
	@echo "APP_NAME=$(APP_NAME)"
	@echo "APP_NAME_UPPER=$(APP_NAME_UPPER)"
	@echo "APP_VSN=$(APP_VSN)"
	@echo "SRC_DIR=$(SRC_DIR)"
	@echo "EBIN_DIR=$(EBIN_DIR)"
	@echo "INCLUDE_DIR=$(INCLUDE_DIR)"
	@echo "DOC_DIR=$(DOC_DIR)"
	@echo "OVERVIEW=$(OVERVIEW)"
	@echo "DOC_INDEX=$(DOC_INDEX)"
	@echo "ERLC_FLAGS=$(ERLC_FLAGS)"
	@echo "SOURCES=$(SOURCES)"
	@echo "INCLUDES=$(INCLUDES)"
	@echo "APP_TARGET=$(APP_TARGET)"
	@echo "APPUP_TARGET=$(APPUP_TARGET)"
	@echo "APP_SRC=$(APP_SRC)"
	@echo "APPUP_SRC=$(APPUP_SRC)"
	@echo "EDOC_OPTS=$(EDOC_OPTS)"
	@echo "EDOC_EXTRA_OPTS=$(EDOC_EXTRA_OPTS)"
	@echo "TEST_MODS=$(TEST_MODS)"
	@echo "MODS_COMMA_LIST=$(MODS_COMMA_LIST)"
	@echo "DEBUG=$(DEBUG)"

#
# diagnostics
#
make_msg = \
 $(if $(call exist,$(1)), o $(1), x $(1))
make_msg_var = $(call make_msg,$($1))

MSG_env_mk := $(call make_msg_var,ENV_MK)
MSG_alt_env_mk := $(call make_msg_var,ALT_ENV_MK)
MSG_vsn_mk := $(call make_msg_var,VSN_MK)
MSG_app_mk := $(call make_msg_var,APP_MK)

MSG_specific_mk := $(call make_msg,$(SPECIFIC_MK))

MSG_overview := $(call make_msg_var,OVERVIEW)
MSG_app_src_full := $(call make_msg_var,app_src_full)
MSG_app_src_simple := $(call make_msg_var,app_src_simple)
MSG_appup_src_full := $(call make_msg_var,appup_src_full)
MSG_appup_src_simple := $(call make_msg_var,appup_src_simple)

.PHONY: hints
hints:
	@echo ""
	@echo FILES:
	@echo ""
	@echo "$(MSG_env_mk)"
	@echo "$(MSG_alt_env_mk)"
	@echo "$(MSG_vsn_mk) (might be automatically created)"
	@echo "$(MSG_app_mk) (might be automatically created)"
	@echo "$(MSG_specific_mk)"
	@echo "$(MSG_overview)"
	@echo "$(MSG_app_src_full)"
	@echo "$(MSG_app_src_simple)"
	@echo "$(MSG_appup_src_full)"
	@echo "$(MSG_appup_src_simple)"
	@echo ""
	@echo VARS:
	@echo ""
	@echo "APP_NAME=$(APP_NAME)"
	@echo "APP_VSN=$(APP_VSN)"
	@echo "SOURCES=$(SOURCES)"
	@echo "NOT_SOURCES=$(NOT_SOURCES)"
	@echo "INCLUDES=$(INCLUDES)"
	@echo "NOT_INCLUDES=$(NOT_INCLUDES)"
	@echo "APP_SRC=$(APP_SRC)"
	@echo "APPUP_SRC=$(APPUP_SRC)"
	@echo "MODS_COMMA_LIST=$(MODS_COMMA_LIST)"
#
# aliases
#
.PHONY: docclean printvars
docclean : doc_clean
printvars : print_vars

# End
